// TODO: CODE REVIEW & CLEANUP!
//
// TouchTextBox.cs
//
// A text box with a label.  Includes touch-friendly UI for selecting the text box.
//

using System;
using System.Windows;
using System.Windows.Controls;
using HomeUX.ClientControlSupport;
using HomeUX.Synchronization;

namespace HomeUX.Controls
{

/// <summary>
/// 
/// </summary>
/// 
/// <remarks>
/// Known limitations:
/// <list type="bullet">
///     <item>
///         In password mode, you cannot use the bullet character 
///         in the password.  Also, if you attempt to copy the password to the clipboard you'll get a series of bullets;
///         you might have expected nothing to copy to the clipboard.  Both these issues are due to Silverlight
///         limitations (lack of selection manipulation members on PasswordBox, required by the Keyboard control) that
///         led to the use of TextBox instead of PasswordBox for passwords.
///         While the Shift button is lit, arrowing left and right shortens and lengthens the selection, as expected,
///         However, if you left-arrow past the beginning of the selection you might expect the selection to begin again
///         in the opposite direction, but instead the insertion point simply moves to the left.  This is due to a
///         Silverlight limitation (no programmatic way to determine or manipulate the insertion point within a
///         selection in a <n>TextBox</n>).
///     </item>
/// </list>
/// </remarks>
[TemplatePart(Name = LayoutRootName, Type = typeof(Panel))]
[TemplatePart(Name = TouchFieldName, Type = typeof(TouchField))]
[TemplatePart(Name = InnerTextBoxName, Type = typeof(TextBox))]
[System.Diagnostics.DebuggerDisplay("TouchTextBox {Label}")]
public class TouchTextBox : Control, IControlHelperConnector
{
    const string LayoutRootName = "LayoutRoot";
    const string TouchFieldName = "TouchField";
    const string InnerTextBoxName = "InnerTextBox";
	/*
    const string CommonStatesName = "CommonStates";
    const string NormalName = "Normal";
    const string MouseOverName = "MouseOver";
    const string FocusStatesName = "FocusStates";
    const string UnfocusedName = "Unfocused";
    const string FocusedName = "Focused";
	*/

    Panel _layoutRoot;
    TouchField _touchField;
    TextBox _innerTextBox;

    public static readonly DependencyProperty LabelProperty =
        DependencyProperty.Register("Label", typeof(object), typeof(TouchTextBox), null);
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(TouchTextBox),
        new PropertyMetadata(OnTextPropertyChanged));
    public static readonly DependencyProperty IsWaitingProperty =
        DependencyProperty.Register("IsWaiting", typeof(bool), typeof(TouchTextBox),
            new PropertyMetadata(TouchTextBox.OnIsWaitingPropertyChanged));

    public bool IsPassword { get; set; }

    public object Label
    {
        get
        {
            return (object)GetValue(LabelProperty);
        }
        set
        {
            SetValue(LabelProperty, value);
        }
    }

    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }
        set
        {
            SetValue(TextProperty, value);
        }
    }

    public TextBox TextBox
    {
        get
        {
            return _innerTextBox;
        }
    }

    public bool IsWaiting
    {
        get
        {
            return (bool)GetValue(IsWaitingProperty);
        }
        set
        {
            SetValue(IsWaitingProperty, value);
        }
    }

    public event TextChangedEventHandler TextChanged;

    public event IsWaitingChangedEventHandler IsWaitingChanged;

    public TouchTextBox()
    {
        DefaultStyleKey = typeof(TouchTextBox);

        IsEnabledChanged += new DependencyPropertyChangedEventHandler(TouchTextBox_IsEnabledChanged);
    }

    void TouchTextBox_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        InvalidateArrange();
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        ControlHelper.GetRequiredTemplateChild(this, LayoutRootName, out _layoutRoot);
        _layoutRoot.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(_layoutRoot_MouseLeftButtonDown);
        ControlHelper.GetRequiredTemplateChild(this, TouchFieldName, out _touchField);
        ControlHelper.GetRequiredTemplateChild(this, InnerTextBoxName, out _innerTextBox);
        _innerTextBox.GotFocus += _innerTextBox_GotFocus;
        _innerTextBox.LostFocus += _innerTextBox_LostFocus;
        if (Text == null)
            Text = String.Empty;
        SetInnerText(Text);
        _innerTextBox.SelectAll();

        // attach to TextChanged event *after* setting text (above)
        _innerTextBox.TextChanged += _innerTextBox_TextChanged;
    }

    void TouchTextBox_GotFocus(object sender, RoutedEventArgs e)
    {
        _innerTextBox.Focus();
    }

    void _layoutRoot_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        _innerTextBox.SelectAll();
        _innerTextBox.Focus();
    }

    void _innerTextBox_GotFocus(object sender, RoutedEventArgs e)
    {
        _touchField.DisplayAsFocused = true;
        _touchField.IsLit = true;
    }

    void _innerTextBox_LostFocus(object sender, RoutedEventArgs e)
    {
        _touchField.DisplayAsFocused = false;
        _touchField.IsLit = false;
    }

    bool _innerTextBoxInitialized = true;//TODO: remove
    char _passwordCharacter = (char)9679;

    // idea: Text property on TouchTextBox contains real value; Text property on _innerTextBox contains bullets
    void _innerTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        //Text = new String((char)9679, 5);
        if (!_innerTextBoxInitialized)
            _innerTextBoxInitialized = true;
        else
        {
            if (IsPassword)
            {
                string newText = UpdatePassword();
                if (newText != null)
                {
                    int insertionPoint = _innerTextBox.SelectionStart;
                    SetInnerText(newText);
                    _innerTextBox.SelectionStart = Math.Min(insertionPoint, newText.Length);
                    Text = newText;
                }
                else
                {
                    SetInnerText(Text);
                    TouchMessageBox.Show(this, "That editing operation is not allowed.", null,
                        delegate(int buttonIndex)
                        {
                            Dispatcher.BeginInvoke(delegate()
                            {
                                _innerTextBox.SelectAll();
                            });
                        });
                }
            }
            else
                Text = _innerTextBox.Text;
            if (TextChanged != null)
                TextChanged(sender, e);
        }
    }

    string UpdatePassword()
    {
        string oldText = Text;
        string innerText = _innerTextBox.Text;

        if (innerText == new String(_passwordCharacter, oldText.Length))
            return oldText;

        if (_innerTextBox.SelectionLength != 0)
            return null; // reject edit: shouldn't have a selection
        // <innerText> should be of the form "<a><b><insertion-point><c>"
        // a = zero or more bullets (or whatever <_passwordCharacter> is)
        // b = zero or more non-bullet characters
        // c = zero or more bullets
        // If <innerText> is not of that form, we reject the edit
        int insertionPoint = _innerTextBox.SelectionStart;
        int index = 0;
        int aCount = 0;
        while ((index < insertionPoint) && (innerText[index] == _passwordCharacter))
        {
            aCount++;
            index++;
        }
        int bCount = 0;
        while (index < insertionPoint)
        {
            if (innerText[index] == _passwordCharacter)
                return null; // reject edit: wrong pattern
            bCount++;
            index++;
        }
        int cCount = 0;
        while (index < innerText.Length)
        {
            if (innerText[index] != _passwordCharacter)
                return null; // reject edit: wrong pattern
            cCount++;
            index++;
        }

        if (aCount + cCount > oldText.Length)
            return null; // reject edit: too many bullets

        return oldText.Substring(0, aCount) + innerText.Substring(aCount, bCount) +
            oldText.Substring(oldText.Length - cCount);
    }

    static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchTextBox)d;
        if (target._innerTextBox != null)
            target.SetInnerText(target.Text);
    }

    void SetInnerText(string text)
    {
        if (IsPassword)
        {
            _innerTextBox.Text = (text != null) ?
                new String(_passwordCharacter, text.Length) : String.Empty;
        }
        else
            _innerTextBox.Text = text ?? String.Empty;
    }

    static void OnIsWaitingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchTextBox)d;
        target._touchField.IsWaiting = (bool)e.NewValue;

        // fire the IsWaitingChanged event
        if (target.IsWaitingChanged != null)
        {
            target.IsWaitingChanged(target, new IsWaitingChangedEventArgs()
            {
                IsWaiting = (bool)e.NewValue
            });
        }
    }

    #region IControlHelperConnector Members

    Control IControlHelperConnector.GetControl()
    {
        return this;
    }

    DependencyObject IControlHelperConnector.GetTemplateChild(string childName)
    {
        return GetTemplateChild(childName);
    }

    #endregion
}

}
